记一次 CVE-2018-8373 利用构造过程
CVE-2018-8373 是趋势科技上个月抓到的一个 vbscript UAF 0day,它是前几个月 CVE-2018-8174 的兄弟漏洞。由于趋势的博文已经把漏洞原理和整个利用过程讲得十分清楚,所以看完文章后的我只有一个问题:如何写一个 exp?
趋势公布的 hash 在 VT 上是没有的,而且文章中只给了 PoC 和部分高度混淆的在野利用片段, 出于这个原因, 我想自己构造一个 exp, 供自己和组内的小伙伴分析调再使用。
由于这个漏洞的触发原理有一点点 0189 的影子,又有一点 8174 的影子,利用部分还有一点点 6332 的影子。所以在熟悉前 3 个漏洞的前提下,写出这个漏洞的 exp 应该不难, 于是我白天花了几个小时试着写了个 exp。
鉴于此漏洞潜在的危害性, 完整代码不予公开,本文仅记录构造过程中的一些细节。
UAF 的原理趋势已经讲的很清楚了, 由于从页堆信息来看,每个二维数组的 tagSAFEARRAY结构占内存空间为 0x30(实际的 tagSAFEARRAY 前面还多申请了 0x10 字节)。 可以仔细看趋势的截图,或者自己动手做一下。
所以为了顺利 UAF,我们需要设计 pvData 部分大小为 0x30 的一维数组,也就是有 3 个元素的一维数组,令其为 array(2)。
我们对照趋势的图 4/图 11, Get P 回调中对 array 数组的大小进行了更改:
array(2)->array(100000),随后利用 array(i) = array2 循环申请大量小空间的二维数组 array2, array2 被定义为 array2(0, 6)。 此过程需要申请大量二维数组的tagSAFEARRAY 结构体,其中有一个会占用原来 array 的 pvAata 内存,并在 Get P 返回之后将 tagSAFEARRAY. rgsabound[1].cElement 覆盖为 0x0fffffff。
所以下一步就是找出哪个 array2 的头部占用了原来 array 的 pvAata 内存, 这个过程可以参考趋势图 16。
思路其实很简单,我们遍历新的 array 数组,找出其中的一维大小为 0x0ffffffe 的 array2。
我们假设这个 array2 在 array 中对应的下标为 index_vul。如下:
找到 array2 之后,我们可以用 array(index_vul)(a, b)来索引这个数组,其中 a∈[0,0xffffffe], b∈[0,1]。这个数组可以越界读写 array(index_vul)(a, b).pvData开始的一大片内存。
现在我们拥有一个可以越界读写的数组,最终目的是要去实现任意地址读写。下一步如何进行?
从趋势的图 15/图 16 可以看到,这个漏洞借助了和 6332 一样的思路来进行利用,即借助大量连续内存申请来实现多个 array2.pvData 内存的连续,每个 array2.pvData 中间间隔 8 字节的堆数据。我们利用越界读写能力通过对 8 字节的错位进行赋值来改变 variant变量在内存中的含义,从而构造一个基地址为 0,元素大小为 1,元素个数为 0x7ffffff 的一维数组;同时再泄露一块内存的地址供读写使用。从而实现任意地址读写。
现在的问题是: 如何找出趋势图 14/15 中的 index_a 和 index_b?关于这点,原文中说得含糊其辞,而且从混淆的片段(图 14)看去步骤较为复杂,还涉及到部分没有出现在代码片段中的函数。
我在放弃理解图 14 的代码片段后,转而开始思考有没有简单一点的办法。从趋势图 15 我们可以看到 index_a 和 index_b 所需要满足的位置。调试器中,我们可以看到 index_a 具有如下特征。
可以看到 array(index_vul)(index_a-8, 0)至 array(index_vul)(index_a-1, 0)都存储着短整型的 3 对应的 variant 结构,到了 array(index_vul)(index_a, 0),由于堆结构数据,错位了 8 个字节,导致 array(index_vul)(index_a, 0)代表的 variant 和前面后不一样。到了 array(index_vul)(index_a+1, 0)。由于我们之前给每个 array2 元素都设置为 3,见趋势图 10。所以 array(index_vul)(index_a+1, 0)开始代表的都是存储着长整型 2 对应的 variant 结构。
VB 有一个函数 VarType,可以用来判断 variant 的类型:
我们可以借助其实现如下逻辑:
这样我们就找到了满足条件的 index_a。
现在我们已经找到了 index_a,如何找到它对应的 index_b 呢?这个则更简单,我们可以利用越界写将 array(index_vul)(index_a, 0)设置为一段字符串,例如“AAAA”,这样某一个 array2 的 Data 域就变成了 8(低 2 字节)。我们遍历 array 数组,找出array(index_b)(0, 0)=8 的 index_b 即可,如下:
当然,上述做法存在一定的失败率,我相信原 exp 中的校验一定会更保险。但是这样的 exp对于本地调试来说,足够了。
在取得 index_a 和 index_b 之后。我们就得到了两个可以用来进行类型混淆的 variant 结构。后续就是借助假的字符串数据和类型混淆构造一个超长一维数组了。
我们模仿 yuange 的 6332 利用代码,令 array(index_vul)(index_a, 0)处存储我们构造的字符串地址,令 array(index_vul)(index_a+2, 0)处存储我们构造的用来伪造数组的字符串地址。然后借助 array(index_b)(0, 0)和 array(index_b)(2, 0)去分别改动两处的类型,令 array(index_vul)(index_a, 0)处的 variant 转化为长整型,令array(index_vul)(index_a, 0)处的 variant 转化为数组,整个过程只需要如下几句代码。 从而获得一个超长数组和一个被泄露的字符串地址。
我们在调试器中看一下上述过程前后 array(index_vul)(index_a+2, 0)的数据变化。
rw_primit 第一句代码执行后:
执行 rw_primit 后:
现在,我们有了一个块泄露的内存 util_mem,一个可以实现 32 位下用户态任意地址读写的一维数组 array(index_vul)(index_a+2, 0),接下来就是利用这两个工具封装任意地址读写函数了。
任意地址读:
任意地址写是一样的原理,此处不再贴出。 相关原理在其他分析6332/0189/8174 的文章中已经有很多详细说明,此处不再提及。
借助上述封装好的函数,将 8174 的利用代码稍微改造一下就可以实现后续步骤:
当然 8174 这种利用方法并不能躲避 ROP 检测, 还是当年的 GodMode 比较经典。
anyway,经过几个小时的努力,我成功地在打了 2018 年 7 月份全补丁的 win7 x86 sp1 IE11 上弹了一个计算器:
像 CVE-2018-8174 和 CVE-2018-8373 这两个漏洞, 思路上基本是 yuange 的漏洞利用技术+TK 的利用技术, 威力可见一斑。
看雪ID:银雁冰
bbs.pediy.com/user-246327
本文由看雪论坛 银雁冰 原创
转载请注明来自看雪社区
热门技术文章推荐: